package com.zj.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zj.dao.RedPacketMapper;
import com.zj.dao.UserRedPacketMapper;
import com.zj.model.RedPacketActivity;
import com.zj.model.UserRedPacket;
import com.zj.restApi.IdGenratorApi;
import com.zj.web.bean.RedPacketActivityDTO;
import com.zj.web.bean.ResponseResult;
import com.zj.web.bean.UserRedPacketDTO;
import jakarta.annotation.PostConstruct;
import lombok.extern.java.Log;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zj
 * @description 红包服务实现类,发红包
 * @date 2024-08-20
 */
@Service
@Log
public class RedPacketServiceImpl implements RedPacketService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private IdGenratorApi idGenratorApi;
    @Autowired
    private RedPacketMapper redPacketMapper;
    @Autowired
    private UserRedPacketMapper userRedPacketMapper;

    // Redis键命名规范：使用前缀+后缀方式，便于管理和查找
    // 红包键：redpacket:红包ID
    private static final String RED_PACKET_KEY_PREFIX = "redpacket:";
    // 金额列表键：redpacket:红包ID:amount_list
    private static final String AMOUNT_LIST_SUFFIX = ":amount_list";
    // 库存键：redpacket:红包ID:stock
    private static final String STOCK_SUFFIX = ":stock";

    /**
     * 发红包核心方法
     * 功能：将总金额拆分为多个小红包，并初始化到Redis中
//     *
//     * @param totalAmount 总金额（单位：分，避免浮点数计算问题）
//     * @param totalCount 红包总个数
     * @return 红包ID（唯一标识）
     */
    @Override
    @Transactional
    public RedPacketActivityDTO publishRedPacket(RedPacketActivityDTO redPacketActivityDTO) {
        log.info("发布红包，参数：{}"+redPacketActivityDTO);
        Double totalAmount = redPacketActivityDTO.getTotalAmount();
        Integer totalCount = redPacketActivityDTO.getTotalPackets();
        // 1. 参数校验
        if (totalAmount <= 0 || totalCount <= 0 || totalAmount < totalCount) {
            throw new IllegalArgumentException("红包参数不合法");
        }

        // 2. 生成唯一红包ID
//        String redPacketId = UUID.randomUUID().toString().replace("-", "");
        ResponseEntity<ResponseResult<Long>> responseEntity = idGenratorApi.getNextId();  //jdk动态代理对象
        if(responseEntity.getStatusCode()!= HttpStatus.OK || responseEntity.getBody()==null){
            throw new RuntimeException("调用idGenerator服务失败");
        }
        ResponseResult<Long> responseResult = responseEntity.getBody();
        log.info(responseResult.toString());
        if (responseResult == null || responseResult.getCode() != 1) {
            throw new RuntimeException("调用idGenerator服务失败");
        }
        Long redPacketId = responseResult.getData();
        redPacketActivityDTO.setActivityId(redPacketId);
        // 3. 使用二倍均值法拆分金额
        List<Double> amountList = splitRedPacketAmount(totalAmount, totalCount);

        // 4. 初始化Redis数据初
        initRedisData(redPacketId, amountList, totalCount);

        // 5. 将红包概要信息存入数据库（创建时间、创建者等）
        redPacketActivityDTO.setStatus(PRODUCT_STATUS_NORMAL);
        RedPacketActivity redPacketActivity = new RedPacketActivity();
        BeanUtils.copyProperties(redPacketActivityDTO,redPacketActivity);
        redPacketMapper.insert(redPacketActivity);

        return redPacketActivityDTO;
    }

    //查询所有的活动信息
    @Override
    public List<RedPacketActivityDTO> getAllRedPacketActivities() {
        LambdaQueryWrapper<RedPacketActivity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(RedPacketActivity::getActivityId);
        List<RedPacketActivity> redPacketActivityList = redPacketMapper.selectList(queryWrapper);
        //根据活动id查询活动状态
        for (RedPacketActivity redPacketActivity : redPacketActivityList) {
            Integer status = getRedPacketActivityStatus(redPacketActivity.getActivityId());
            redPacketActivity.setStatus(status);
        }
        //将实体类转换为DTO类
        List<RedPacketActivityDTO> redPacketActivityDTOList = redPacketActivityList.stream()
                .map(redPacketActivity -> {
                    RedPacketActivityDTO redPacketActivityDTO = new RedPacketActivityDTO();
                    BeanUtils.copyProperties(redPacketActivity, redPacketActivityDTO);
                    return redPacketActivityDTO;
                })
                .collect(Collectors.toList());
        return redPacketActivityDTOList;
    }
    //根据 活动id查询活动状态
    @Override
    public Integer getRedPacketActivityStatus(Long activityId) {
        RedPacketActivity redPacketActivity = redPacketMapper.selectById(activityId);
        //没有这个红包活动
        if(redPacketActivity==null){
            return null;
        }
        //活动未开始
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(redPacketActivity.getStartTime())) {
            redPacketActivity.setStatus(PRODUCT_STATUS_NORMAL);
            redPacketMapper.updateById(redPacketActivity);
            return redPacketActivity.getStatus();
        }

        // 活动结束了或者红包被抢完了
        String status = checkRedPacketStatus(activityId);
        if ("STOCK_EMPTY".equals(status)||"AMOUNT_LIST_EMPTY".equals(status)||now.isAfter(redPacketActivity.getEndTime())) {
            redPacketActivity.setStatus(PRODUCT_STATUS_FINISHED);
            redPacketMapper.updateById(redPacketActivity);
            return redPacketActivity.getStatus();
        }

        //活动进行中
        redPacketActivity.setStatus(PRODUCT_STATUS_RUNNING);
        redPacketMapper.updateById(redPacketActivity);
        return redPacketActivity.getStatus();
    }

    //查询最新的活动信息
    @Override
    public RedPacketActivityDTO getRedPacketActivity() {
        RedPacketActivity redPacketActivity = redPacketMapper.getLatestRedPacketActivity();
        if(redPacketActivity==null){
            return null;
        }
        RedPacketActivityDTO redPacketActivityDTO = new RedPacketActivityDTO();
        BeanUtils.copyProperties(redPacketActivity, redPacketActivityDTO);
        return redPacketActivityDTO;
    }

    /**
     * 二倍均值法拆分红包金额
     * 功能：保证每个红包金额相对公平，避免极端情况
     */
    private List<Double> splitRedPacketAmount(Double totalAmount, int totalCount) {
        List<Double> amountList = new ArrayList<>();
        int restAmount = (int) (totalAmount * 100);  // 转换为分
        int restCount = totalCount;
        Random random = new Random();

        for (int i = 0; i < totalCount - 1; i++) {
            int avg = restAmount / restCount;
            int max = avg * 2 - 1;  // 保证至少1分钱
            int amount = random.nextInt(max) + 1;

            amountList.add(amount / 100.0);  // 转换为元，保留两位小数
            restAmount -= amount;
            restCount--;
        }

        // 最后一个红包
        amountList.add(restAmount / 100.0);  // 转换为元
        return amountList;
    }

    /**
     * 初始化Redis数据
     * 功能：将拆分好的红包数据存入Redis，为抢红包做准备
     */
    private void initRedisData(Long redPacketId, List<Double> amountList, int totalCount) {
        String stockKey = RED_PACKET_KEY_PREFIX + redPacketId + STOCK_SUFFIX;
        String amountListKey = RED_PACKET_KEY_PREFIX + redPacketId + AMOUNT_LIST_SUFFIX;

        // 1. 设置库存数量
        redisTemplate.opsForValue().set(stockKey, String.valueOf(totalCount));

        // 2. 将金额列表存入Redis List（由于LPOP是从左边弹出，需要反转列表）
        Collections.reverse(amountList);  // 反转列表，保证弹出的顺序正确
        String[] amountArray = amountList.stream()
                .map(String::valueOf)
                .toArray(String[]::new);
        redisTemplate.opsForList().rightPushAll(amountListKey, amountArray);

        // 3. 设置过期时间（防止僵尸数据占用内存）
        redisTemplate.expire(stockKey, Duration.ofHours(24));      // 24小时后过期
        redisTemplate.expire(amountListKey, Duration.ofHours(24)); // 24小时后过期

        log.info("红包初始化成功: redPacketId={"+ redPacketId+"}, totalCount={"+totalCount+"}");
    }

    //===============================     抢红包       =====================
    private DefaultRedisScript<String> grabRedPacketScript;

    /**
     * 初始化方法：加载Lua脚本
     * 功能：在Bean创建后自动执行，预加载Lua脚本到Redis
     */
    @PostConstruct
    public void init() {
        grabRedPacketScript = new DefaultRedisScript<>();
        grabRedPacketScript.setLocation(new ClassPathResource("scripts/grab_red_packet.lua"));
        grabRedPacketScript.setResultType(String.class);  // 设置返回类型为字符串
    }

    @Override
    @Transactional
    public GrabResult grabRedPacket(Long redPacketId, Long userId) {
        RedPacketActivity redPacketActivity = redPacketMapper.selectById(redPacketId);
        try {
            // 前置检查红包状态
            String status = checkRedPacketStatus(redPacketId);
            if (!"AVAILABLE".equals(status)) {
                switch (status) {
                    case "RED_PACKET_NOT_EXISTS":
                        return new GrabResult(false, "红包不存在", null, new Date());
                    case "STOCK_EMPTY":
                    case "AMOUNT_LIST_EMPTY":
                        // 红包已抢完，更新状态为已结束
                        redPacketActivity.setStatus(PRODUCT_STATUS_FINISHED);
                        redPacketMapper.updateById(redPacketActivity);
                        return new GrabResult(false, "红包已抢完", null, new Date());
                    default:
                        return new GrabResult(false, "系统错误", null, new Date());
                }
            }

            // 执行Lua脚本抢红包
            GrabResult grabResult = executeGrabRedPacketLua(redPacketId, userId);

            // 抢红包成功后检查是否已抢完
            if (grabResult.isSuccess()) {
                String newStatus = checkRedPacketStatus(redPacketId);
                if ("STOCK_EMPTY".equals(newStatus) || "AMOUNT_LIST_EMPTY".equals(newStatus)) {
                    redPacketActivity.setStatus(PRODUCT_STATUS_FINISHED);
                    redPacketMapper.updateById(redPacketActivity);
                }
            }

            return grabResult;
        } catch (Exception e) {
            log.severe("抢红包异常: " + e.getMessage());
            return new GrabResult(false, "系统繁忙，请稍后重试", null, new Date());
        }
    }

    //将用户抢红包的信息添加到数据库
    @Override
    @Transactional
    public UserRedPacketDTO addUserRedPacket(UserRedPacketDTO userRedPacketDTO) {
        //生成Id
        ResponseEntity<ResponseResult<Long>> responseEntity = idGenratorApi.getNextId();  //jdk动态代理对象
        if(responseEntity.getStatusCode()!= HttpStatus.OK || responseEntity.getBody()==null){
            throw new RuntimeException("调用idGenerator服务失败");
        }
        ResponseResult<Long> responseResult = responseEntity.getBody();
        log.info(responseResult.toString());
        if (responseResult == null || responseResult.getCode() != 1) {
            throw new RuntimeException("调用idGenerator服务失败");
        }
        Long id = responseResult.getData();
        userRedPacketDTO.setPacketId(id);
        //添加到数据库
        UserRedPacket userRedPacket = new UserRedPacket();
        BeanUtils.copyProperties(userRedPacketDTO,userRedPacket);
        userRedPacketMapper.insert(userRedPacket);
        return userRedPacketDTO;
    }

    private GrabResult executeGrabRedPacketLua(Long redPacketId, Long userId){
        String stockKey = RED_PACKET_KEY_PREFIX + redPacketId + STOCK_SUFFIX;
        String amountListKey = RED_PACKET_KEY_PREFIX + redPacketId + AMOUNT_LIST_SUFFIX;
        String grabedUsersKey = RED_PACKET_KEY_PREFIX + redPacketId + ":grabed_users";

        log.info("执行抢红包: userId=" + userId + ", redPacketId=" + redPacketId);

        String result = redisTemplate.execute(
                grabRedPacketScript,
                Arrays.asList(stockKey, amountListKey, grabedUsersKey, userId.toString())//KEYS[1]~KEYS[4]
        );
        log.info("===============================Lua脚本执行结果: redPacketId=" + redPacketId + ", result=" + result);
        log.info("===================================result="+result);


        // 处理Lua脚本返回结果
        if (result == null) {
            log.warning("Lua脚本返回null: redPacketId=" + redPacketId);
            return new GrabResult(false, "系统错误", null, new Date());
        }

        switch (result) {
            case "RED_PACKET_NOT_EXISTS":
                log.warning("红包不存在: redPacketId=" + redPacketId);
                return new GrabResult(false, "红包不存在", null, new Date());

            case "USER_ALREADY_GRABED":
                log.info("用户已抢过: userId=" + userId + ", redPacketId=" + redPacketId);
                return new GrabResult(false, "您已经抢过该红包", null, new Date());

            case "STOCK_EMPTY":
            case "AMOUNT_LIST_EMPTY":
                log.info("红包已抢完: redPacketId=" + redPacketId);
                return new GrabResult(false, "红包已抢完", null, new Date());

            case "STOCK_ERROR":
            case "ERROR_PARAMS":
                log.severe("系统数据异常: " + result + ", redPacketId=" + redPacketId);
                return new GrabResult(false, "系统错误", null, new Date());

            default:
                // 尝试解析金额（成功情况）
                try {
                    double amount = Double.parseDouble(result);
                    log.info("抢红包成功: userId=" + userId + ", amount=" + amount);
                    return new GrabResult(true, "抢红包成功", amount, new Date());
                } catch (NumberFormatException e) {
                    log.severe("金额解析错误: " + result);
                    return new GrabResult(false, "系统错误", null, new Date());
                }
        }
    }
    /**
     * 检查红包状态
     */
    private String checkRedPacketStatus(Long redPacketId) {
        String stockKey = RED_PACKET_KEY_PREFIX + redPacketId + STOCK_SUFFIX;
        String amountListKey = RED_PACKET_KEY_PREFIX + redPacketId + AMOUNT_LIST_SUFFIX;

        try {
            // 检查key是否存在
            Boolean existsStock = redisTemplate.hasKey(stockKey);
            Boolean existsAmountList = redisTemplate.hasKey(amountListKey);
            log.info("=======================================当前这个红包的key是否存在:+"+existsStock+",amountListKey是否存在:"+existsAmountList);
            if (!existsStock ) {
                return "RED_PACKET_NOT_EXISTS";
            }

            // 检查库存
            String stockStr = redisTemplate.opsForValue().get(stockKey);
            log.info("=======================================当前这个红包的库存为:"+stockStr);
            if (stockStr == null) {
                return "STOCK_ERROR";
            }

            int stock = Integer.parseInt(stockStr);
            log.info("=======================================当前这个红包的库存为:"+stock);
            if (stock <= 0 || !existsAmountList) {
                return "STOCK_EMPTY";
            }

            // 检查金额列表长度
            Long amountListSize = redisTemplate.opsForList().size(amountListKey);
            if (amountListSize == null || amountListSize <= 0) {
                return "AMOUNT_LIST_EMPTY";
            }

            return "AVAILABLE";
        } catch (Exception e) {
            log.severe("检查红包状态异常: " + e.getMessage());
            return "CHECK_ERROR";
        }
    }
}
